package scan

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// DirInfo 目录信息
type DirInfo struct {
	Directories [][]string // 目录列表
	Files       [][]string // 文件列表
}

// Scanner 扫描器接口
type Scanner interface {
	ScanDirectory(rootDir string, exclude []string, scanSubdirs bool) (*DirInfo, error)
}

// scannerImpl 扫描器实现
type scannerImpl struct {
	relativePath   bool                                          // 是否返回相对路径
	toRelativePath func(rootDir, absPath string) (string, error) // 转换为相对路径
}

// New 创建一个 Scanner 对象
// relativePath: 是否返回相对路径，默认为 false，即返回绝对路径
func New(relativePath bool) Scanner {
	var toRelativePath func(rootDir, absPath string) (string, error)
	if relativePath {
		toRelativePath = func(rootDir, absPath string) (string, error) {
			return filepath.Rel(rootDir, absPath)
		}
	} else {
		toRelativePath = func(rootDir, absPath string) (string, error) {
			return absPath, nil
		}
	}
	return &scannerImpl{
		relativePath:   relativePath,
		toRelativePath: toRelativePath,
	}
}

// ScanDirectory 扫描指定目录下的所有子目录和子文件，并返回路径列表
// rootDir: 要扫描的目录
// exclude: 要排除的目录或文件名列表
func (s *scannerImpl) ScanDirectory(rootDir string, exclude []string, scanSubdirs bool) (*DirInfo, error) {
	// 校验参数
	if rootDir == "" {
		return nil, errors.New("rootDir 不能为空")
	}
	// 规范化路径
	rootDir = s.normalizeDrivePath(rootDir)
	// 获取目录信息
	dirInfo, err := os.Stat(rootDir)
	if err != nil {
		return nil, err
	}
	// 校验 rootDir 是否是一个目录
	if !dirInfo.IsDir() {
		return nil, errors.New("rootDir 必须是一个目录")
	}
	// 创建过滤器
	filter := newFilter(exclude, true)

	// 准备结果

	if scanSubdirs {
		return s.scanDirectoryAllLevels(rootDir, filter)
	} else {
		// 仅扫描当前目录
		return s.scanDirectoryCurrentLevel(rootDir, filter)
	}
}

// 扫描当前目录的函数
func (s *scannerImpl) scanDirectoryCurrentLevel(rootDir string, filter *filter) (*DirInfo, error) {
	entries, err := os.ReadDir(rootDir)
	if err != nil {
		return nil, err
	}
	dirPaths := make([]string, 0)
	filePaths := make([]string, 0)
	for _, entry := range entries {
		name := entry.Name()
		path := filepath.Join(rootDir, name)
		if filter.isExcluded(name) {
			continue // 跳过被排除的项
		}
		// 转换为相对路径
		var relPath string
		if s.relativePath {
			if relPath, err = s.toRelativePath(rootDir, path); err != nil {
				return nil, err
			}
		} else {
			relPath = path
		}
		if entry.IsDir() {
			dirPaths = append(dirPaths, relPath)
		} else {
			filePaths = append(filePaths, relPath)
		}
	}
	return &DirInfo{
		Directories: s.splitPaths(dirPaths),
		Files:       s.splitPaths(filePaths),
	}, nil
}

// 扫描所有子目录的函数
func (s *scannerImpl) scanDirectoryAllLevels(rootDir string, filter *filter) (*DirInfo, error) {
	dirPaths := make([]string, 0)
	filePaths := make([]string, 0)
	// 使用 filepath.WalkDir 遍历目录
	err := filepath.WalkDir(rootDir, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return s.handleWalkError(path, err) // 处理错误
		}

		name := d.Name()
		// 判断当前处理的文件或目录是否在排除列表中
		if filter.isExcluded(name) {
			if d.IsDir() {
				return filepath.SkipDir // 如果是目录，使用 SkipDir 跳过该目录及其所有子内容
			}
			return nil // 对于文件，直接返回 nil，表示跳过该文件
		}

		// 转换为相对路径
		var relPath string
		if s.relativePath {
			if relPath, err = s.toRelativePath(rootDir, path); err != nil {
				return err
			}
		} else {
			relPath = path
		}
		if d.IsDir() {
			dirPaths = append(dirPaths, relPath)
		} else {
			filePaths = append(filePaths, relPath)
		}
		// 将文件或目录添加到结果列表

		return nil
	})
	// 返回结果
	if err != nil {
		return nil, err
	}
	// 返回结果
	return &DirInfo{
		Directories: s.splitPaths(dirPaths),
		Files:       s.splitPaths(filePaths),
	}, nil
}

// 处理 WalkDir 时的错误
func (s *scannerImpl) handleWalkError(path string, err error) error {
	if os.IsPermission(err) || os.IsNotExist(err) {
		fmt.Printf("Skipping directory: %s due to error: %v\n", path, err)
		return nil // 跳过当前目录
	}
	return err // 其他错误返回
}

// 以下是一些辅助函数

// splitPath 将路径分割为数组
func (s *scannerImpl) splitPath(path string) []string {
	// 获取系统的路径分隔符
	separator := string(os.PathSeparator)
	// 使用 strings.Split 分割路径
	result := strings.Split(path, separator)
	if len(result) <= 1 {
		return result
	}
	result[0] = s.normalizeDrivePath(result[0])
	return result
}

// splitPaths 将路径数组分割为二维数组
func (s *scannerImpl) splitPaths(paths []string) [][]string {
	result := make([][]string, 0)
	for _, path := range paths {
		result = append(result, s.splitPath(path))
	}
	return result
}

// isWindowsDrive 判断路径是否是 Windows 盘符
func (s *scannerImpl) isWindowsDrive(path string) bool {
	// 在 Windows 系统上，路径盘符是带冒号的，而路径的其他位置是不带冒号的
	// 因为冒号在系统下是一个特殊符号，不能作为文件名或目录名的一部分。
	path = strings.Trim(path, `\/`)                        // 去掉路径两边的分隔符
	array := strings.Split(path, string(os.PathSeparator)) // 按系统路径分隔符分割路径
	if len(array) != 1 {                                   // 不止一个元素，说明不是盘符
		return false
	}
	// 只有一个元素，判断是否有冒号
	return strings.Contains(path, ":")
}

// normalizeDrivePath 规范化路径
func (s *scannerImpl) normalizeDrivePath(path string) string {
	// 检查路径是不是 Windows 盘符
	if !s.isWindowsDrive(path) {
		return path // 不是 Windows 盘符，直接返回
	}
	// 规范化路径，将盘符后面的路径分隔符替换为系统的路径分隔符
	path = strings.Trim(path, `\/`)
	separator := string(os.PathSeparator)
	return path + separator
}
